package structure;

import java.util.ArrayList;

import algorithm.algorithm;
import algorithm.miscellaneous;
/*
 * è il grafo di transizione, l'isieme delle big production collegate da archi epsilon
 * lo rappresento con un arraylist di array di stringhe, dove l'array di stringhe ha 3 posizioni possibili
 * la prima è l'item di partenza, e quindi in riga
 * la seconda è il valore dell'arco, e quindi epsilon
 * la terza è l'item di arrivo, e quindi in colonna
 */
public class graph {

	private ArrayList<bigProduction> bigProduction;
	private ArrayList<bigProductionWithLookahead> bigProductionWithLookahead;
	private ArrayList<item> items;
	private ArrayList<item[]> connectionIntraBP;
	private ArrayList<item[]> connectionInterBP;
	
	private static String dollar= "$";
	private static String dot= ".";
	
	//costruttori
	public graph(){

		this.bigProduction= new ArrayList<bigProduction>();
		this.bigProductionWithLookahead= new ArrayList<bigProductionWithLookahead>();
		this.items= new ArrayList<item>();
		this.connectionIntraBP= new ArrayList<item[]>();
		this.connectionInterBP= new ArrayList<item[]>();
	}
	
	public graph(enumerateGrammar e){

		this.bigProduction= new ArrayList<bigProduction>();
		this.bigProductionWithLookahead= new ArrayList<bigProductionWithLookahead>();
		this.items= new ArrayList<item>();
		this.connectionIntraBP= new ArrayList<item[]>();
		this.connectionInterBP= new ArrayList<item[]>();
		
		this.createBigProduction(e);
		
		this.setLookahead(e);
		
		this.connectIntraBP(e);
		
		this.connectInterBP(e);
		
	}
	//

	//crea le big production
	public void createBigProduction(enumerateGrammar e){
		
		for(int i=0; i<e.getLeftside().size(); i++){
			
			bigProduction big= new bigProduction(e, i);
			this.bigProduction.add(big);
		}
		
		this.createItems();
		
	}
	
	//get
	private ArrayList<ArrayList<String>> getLookaheadByLeftside(String leftside){
		
		ArrayList<ArrayList<String>> toReturn= new ArrayList<ArrayList<String>>();
		
		for(bigProductionWithLookahead bigL : this.bigProductionWithLookahead){
			
			if(bigL.getLeftside().equals(leftside))
				toReturn.add(bigL.getLookahead());
		}
		
		return toReturn;
	}
	
	public ArrayList<bigProduction> getBigProductionByLeftside(String leftside){
		
		ArrayList<bigProduction> toReturn= new ArrayList<bigProduction>();
		for(bigProduction big : this.bigProduction){
			
			if(big.getLeftside().equals(leftside)){
				
				toReturn.add(big);
			}
		}
		
		return toReturn;
	}
	
	public ArrayList<bigProductionWithLookahead> getBigProductionWLByLeftside(String leftside){
		
		ArrayList<bigProductionWithLookahead> toReturn= new ArrayList<bigProductionWithLookahead>();
		for(bigProductionWithLookahead bigWL : this.bigProductionWithLookahead){
			
			if(bigWL.getLeftside().equals(leftside)){
				
				toReturn.add(bigWL);
			}
		}
		
		return toReturn;
	}
	
	public ArrayList<item> getItemByLeftside(String left){
		
		ArrayList<item> toReturn= new ArrayList<item>();
		for(item i : this.items){
			
			if(i.getLeftside().equals(left))
				toReturn.add(i);
		}
		
		return toReturn;
	}
	
	public ArrayList<item[]> getConnectionIntraBP(){
		
		return this.connectionIntraBP;
	}
	
	public ArrayList<item[]> getConnectionInterBP(){
		
		return this.connectionInterBP;
	}
	
	public ArrayList<item> getItems(){
		
		return this.items;
	}
	//

	/*
	 * crea il lookahead set nel seguente modo:
	 * - se S è l'assioma L'= {$}
	 * - se (B->a.Sb, L) e b=e allora L'= L
	 * - se (B->a.Sb, L) e b!=e e b->*e allora L'= L U (First1(b)-{e})
	 * - se (B->a.Sb, L) e b!=e e b!->*e allora L'= First1(b)
	 */
	private void setLookahead(enumerateGrammar e){
		
		ArrayList<String> leftside= e.getLeftside();
		ArrayList<String> nullable= e.getNullable();
		if(nullable == null) nullable= new ArrayList<String>();
		ArrayList<String> tmpLook;
		ArrayList<item> toAdd= new ArrayList<item>();
		for(int i=0; i<this.items.size(); i++){
			
			item tmpI= this.items.get(i);
			String right= tmpI.getRightside();
			String left= tmpI.getLeftside();
			ArrayList<String> lookahead= tmpI.getLookahead();
			
			//caso dell'assioma
			if(left.equals(e.getAxiom())){
				
				tmpLook= new ArrayList<String>();
				tmpLook.add(dollar);
				tmpI.setLookahead(tmpLook);
			}
			
			String symbolAfterDot= miscellaneous.getSymbolAfterDot(right);
			
			if(!symbolAfterDot.equals("") && leftside.contains(symbolAfterDot)){
				
				ArrayList<item> alItem= this.getItemByLeftside(symbolAfterDot);
				
				for(item tmpItem : alItem){
					
					int pos= 0;
					char[] c_r= right.toCharArray();
					
					//prendo la posizione nella stringa del simbolo dopo il punto
					for(int a=0; a<c_r.length-1; a++){
						if(c_r[a] == dot.charAt(0))
							pos= a+1;
					}
					
					//assegno beta
					String beta= "";
					if(right.length()-1-pos == 1){
						
						beta= right.substring(pos+1);
					}
					else if(right.length()-1-pos > 1){
						
						beta= right.substring(pos+1);
						beta= beta.substring(0, 1);
					}
					
					tmpLook= new ArrayList<String>();
				
					if(tmpItem.getLookahead().isEmpty()){
						tmpLook= this.calculateLookahead(tmpItem, beta, lookahead, right, nullable, e);
						tmpItem.setLookahead(tmpLook);
					}
					else{
						tmpLook= this.calculateLookahead(tmpItem, beta, lookahead, right, nullable, e);
						item tmpII= new item(tmpItem.getLeftside(), tmpItem.getRightside(), tmpLook);
						toAdd.add(tmpII);
					}
				}
				
			}
		}
		
		miscellaneous.addAllItemOnce(toAdd, this.items);
	}
	
	private void createItems(){

		for(bigProduction bigL : this.bigProduction){
			
			ArrayList<String> rightside= bigL.getRightside();
			
			for(String right : rightside){
				
				item tmpI= new item(bigL.getLeftside(), right, null);
				this.items.add(tmpI);
			}
		}
	}
	
	private void connectIntraBP(enumerateGrammar e){

		for(item i : this.items){
			
			for(item ii : this.items){
				
				//stesso leftside
				if(i.getLeftside().equals(ii.getLeftside()) && ii.sameLookahead(i.getLookahead())){
					
					String rightI= i.getRightside();
					String rightII= ii.getRightside();
					if(rightI.length() == rightII.length() && !rightI.equals(rightII)){
						
						char[] charRightI= rightI.toCharArray();
						char[] charRightII= rightII.toCharArray();
						
						for(int index=0; index<charRightI.length-1; index++){
							//prendo la posizione del punto
							if(charRightI[index] == dot.charAt(0)){
								//se nella posizione index+1 della seconda stringa ho un punto
								//sono connesse
								if(charRightII[index+1] == dot.charAt(0)){
									
									item[] tmpItem= {i, ii};
									this.connectionIntraBP.add(tmpItem);
								}
							}
						}
					}
				}
			}
			
		}
	}
	
	private void connectInterBP(enumerateGrammar e){
		
		ArrayList<String> leftside= e.getLeftside();
		ArrayList<String> nullable= e.getNullable();
		if(nullable == null) nullable= new ArrayList<String>();
		ArrayList<String> tmpLook;
		
		for(int i=0; i<this.items.size(); i++){
			
			item tmpI= this.items.get(i);
			String right= tmpI.getRightside();
			ArrayList<String> lookahead= tmpI.getLookahead();
			
			String symbolAfterDot= miscellaneous.getSymbolAfterDot(right);
			
			if(!symbolAfterDot.equals("") && leftside.contains(symbolAfterDot)){
				
				ArrayList<item> alItem= this.getItemByLeftside(symbolAfterDot);
				
				for(item tmpItem : alItem){
					
					int pos= 0;
					char[] c_r= right.toCharArray();
					
					//prendo la posizione nella stringa del simbolo dopo il punto
					for(int a=0; a<c_r.length-1; a++){
						if(c_r[a] == dot.charAt(0))
							pos= a+1;
					}
					
					//assegno beta
					String beta= "";
					if(right.length()-1-pos >= 1){
						
						beta= right.substring(pos+1);
					}
					/*else if(right.length()-1-pos > 1){
						
						beta= right.substring(pos+1);
						beta= beta.substring(0, 1);
					}*/
					//System.out.println("beta= " + beta);
					
					tmpLook= new ArrayList<String>();
					
					tmpLook= this.calculateLookahead(tmpItem, beta, lookahead, right, nullable, e);
					
					String r= tmpItem.getRightside();
					if(r.substring(0, 1).equals(dot) && tmpItem.sameLookahead(tmpLook) &&
							!tmpI.sameAs(tmpItem)){
						
						item[] tmpItems= {tmpI, tmpItem};
						this.connectionInterBP.add(tmpItems);
					}
					
				}
				
			}
		}
	}
	
	private ArrayList<String> calculateLookahead(item tmpItem, String beta, 
								ArrayList<String> lookahead, String right, ArrayList<String> nullable,
								enumerateGrammar e){
		
		ArrayList<String> toReturn= new ArrayList<String>();
		//caso di beta = stringa vuota
		if(beta.equals("")){
			
			miscellaneous.addAllOnce(lookahead, toReturn);
		}
		//caso beta nullabile
		else if(!beta.equals("") && nullable.contains(beta)){
			
			miscellaneous.addAllOnce(lookahead, toReturn);
			
			ArrayList<String> first= algorithm.First1(e, beta, null, null);
			
			int toRemove=-1;
			for(int k=0; k<first.size(); k++){
				
				if(first.get(k).equals("e")){
					toRemove= k;
					break;
				}
			}
			
			if(toRemove >= 0)
				first.remove(toRemove);

			miscellaneous.addAllOnce(first, toReturn);
		}
		else{
			
			ArrayList<String> first= algorithm.First1(e, beta, null, null);

			miscellaneous.addAllOnce(first, toReturn);
		}
		
		return toReturn;
	}
	
	//controllo se ho gia inserito quella bigProductionWithLookahead
	public boolean isAlreadyInsered(bigProductionWithLookahead bigL){
		
		for(bigProductionWithLookahead big : this.bigProductionWithLookahead){
			
			//uguali leftside
			if(big.getLeftside().equals(bigL.getLeftside())){
				
				if(big.sameRightside(bigL.getRightside())){
					
					if(big.sameLookahead(bigL.getLookahead())){
						
						return true;
					}
				}
			}
		}
		
		return false;
	}
	
	public void printItAll(){

		System.out.println("---------------------------------");
		System.out.println("START GRAPH");

		System.out.println("START BIG PRODUCTION");
		for(bigProduction big : this.bigProduction){
			big.printItAll();
		}
		System.out.println("END BIG PRODUCTION");
		for(bigProductionWithLookahead bigL : this.bigProductionWithLookahead){
			bigL.printItAll();
		}
		
		System.out.println("END GRAPH");
		System.out.println("---------------------------------");
		
		for(item i : this.items){
			
			i.printItAll();
		}
		System.out.println("---------------------------------");
		System.out.println("Intra");
		
		for(item[] i : this.connectionIntraBP){

			System.out.print("Item1= "); i[0].printItAll();
			System.out.print("Item2= "); i[1].printItAll();
			
		}
		System.out.println("---------------------------------");
		System.out.println("Inter");
		
		for(item[] i : this.connectionInterBP){

			System.out.print("Item1= "); i[0].printItAll();
			System.out.print("Item2= "); i[1].printItAll();
			
		}
	}
	

}
